// The NSpring Framework for .NET
// Copyright (c) 2003, Jeffrey Varszegi
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
// 
// - Redistributions of source code must retain the above copyright notice, this 
// list of conditions and the following disclaimer.
// 
// - Redistributions in binary form must reproduce the above copyright notice, this 
// list of conditions and the following disclaimer in the documentation and/or other 
// materials provided with the distribution.
// 
// - Neither the name of the NSpring project nor the names of its contributors may 
// be used to endorse or promote products derived from this software without 
// specific prior written permission from the copyright owner.
// 
// - No product derived from this software may be called "NSpring", nor may 
// "NSpring" appear in the name of such a product, without specific prior written 
// permission from the copyright owner.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
// OF THE POSSIBILITY OF SUCH DAMAGE. 

namespace NSpring.Logging.Filters {

using System;
using System.Collections;

/// <summary>
/// Provides a filter that accepts and rejects Event objects by matching on each Event's 
/// Category property.
/// </summary>
public sealed class CategoryFilter : Filter {
    private static object NULL                          = new object();

    private Hashtable categoriesMap                     = new Hashtable(100);
    private string[] categoriesArray;
    private int categoryCount;

    private bool isSingleCategory;
    private string singleCategory;

    private readonly object monitor                     = new object();

    private CategoryFilter() {}

    /// <summary>
    /// Constructs a new  instance that matches based on the specified value
    /// </summary>
    /// <param name="category">The value on which to match</param>
    public CategoryFilter(string category) {
        categoriesArray = new string[1];
        categoriesArray[0] = category;
        isSingleCategory = true;
        singleCategory = category;
        if (category == null) {
            categoriesMap.Add(NULL, NULL);
        }
        else {
            categoriesMap.Add(category, category);
        }
        categoryCount = 1;
    }

    /// <summary>
    /// Constructs a new  instance that matches based on the specified value
    /// </summary>
    /// <param name="category">The value on which to match</param>
    /// <param name="inclusive">Whether or not the filter is inclusive</param>
    public CategoryFilter(string category, bool inclusive) {
        categoriesArray = new string[1];
        categoriesArray[0] = category;
        isSingleCategory = true;
        singleCategory = category;
        if (category == null) {
            categoriesMap.Add(NULL, NULL);
        }
        else {
            categoriesMap.Add(category, category);
        }
        categoryCount = 1;
        isInclusive = inclusive;
    }

    /// <summary>
    /// Constructs a new  instance that matches based on the specified values
    /// </summary>
    /// <param name="categories">The values on which to match</param>
    public CategoryFilter(string[] categories) {
        if (categories == null) {
            throw new ArgumentNullException();
        }
        else if (categories.Length == 0) {
            throw new ArgumentException("Must add at least one EventCategory");
        }

        object key;
        for(int x = 0; x < categories.Length; x++) {
            key = categories[x];
            if (key == null) {
                key = NULL;
            }
            if (!categoriesMap.ContainsKey(key)) {
                categoriesMap.Add(key, key);
            }
        }

        categoryCount = categoriesMap.Keys.Count;
        if (categoryCount == 1) {
            categoriesArray = new string[1];
            categoriesArray[0] = singleCategory;
            isSingleCategory = true;
        }
        else {
            isSingleCategory = false;
            singleCategory = null;
            categoriesArray = new string[categoryCount];
            IEnumerator e = categoriesMap.Keys.GetEnumerator();
            for(int x = 0; x < categoryCount; x++) {
                e.MoveNext();
                key = e.Current;
                if (key == NULL) {
                    categoriesArray[x] = null;
                }
                else {
                    categoriesArray[x] = (string)key;
                }
            }
        }
    }

    /// <summary>
    /// Constructs a new  instance that matches based on the specified values
    /// </summary>
    /// <param name="categories">The values on which to match</param>
    /// <param name="inclusive">Whether or not the filter is inclusive</param>
    public CategoryFilter(string[] categories, bool inclusive) {
        if (categories == null) {
            throw new ArgumentNullException();
        }
        else if (categories.Length == 0) {
            throw new ArgumentException("Must add at least one EventCategory");
        }

        object key;
        for(int x = 0; x < categories.Length; x++) {
            key = categories[x];
            if (key == null) {
                key = NULL;
            }
            if (!categoriesMap.ContainsKey(key)) {
                categoriesMap.Add(key, key);
            }
        }

        categoryCount = categoriesMap.Keys.Count;
        if (categoryCount == 1) {
            categoriesArray = new string[1];
            categoriesArray[0] = singleCategory;
            isSingleCategory = true;
        }
        else {
            isSingleCategory = false;
            singleCategory = null;
            categoriesArray = new string[categoryCount];
            IEnumerator e = categoriesMap.Keys.GetEnumerator();
            for(int x = 0; x < categoryCount; x++) {
                e.MoveNext();
                key = e.Current;
                if (key == NULL) {
                    categoriesArray[x] = null;
                }
                else {
                    categoriesArray[x] = (string)key;
                }
            }
        }
        isInclusive = inclusive;
    }

    /// <summary>
    /// Adds a value to the list matched on by this instance
    /// </summary>
    /// <param name="category">The value to add</param>
    public void AddCategory(string category) {
        if (category == null) {
            lock (monitor) {
                if (!categoriesMap.ContainsKey(NULL)) {
                    categoriesMap.Add(NULL, NULL);
                    categoriesArray = null;
                    categoryCount++;
                    isSingleCategory = false;
                    singleCategory = null;
                }
            }
        }
        else {
            lock (monitor) {
                if (!categoriesMap.ContainsKey(category)) {
                    categoriesMap.Add(category, category);
                    categoriesArray = null;
                    categoryCount++;
                    isSingleCategory = false;
                    singleCategory = null;
                }
            }
        }
    }

    /// <summary>
    /// Removes a value from the list matched on by this instance
    /// </summary>
    /// <param name="category">The value to remove</param>
    public void RemoveCategory(string category) {
        if (category == null) {
            lock (monitor) {
                if (categoryCount == 1) {
                    throw new ApplicationException("Can't remove the last EventCategory from the filter");
                }
                if (categoriesMap.ContainsKey(NULL)) {
                    categoriesMap.Remove(NULL);
                    categoriesArray = null;
                    categoryCount--;
                }
            }
        }
        else {
            lock (monitor) {
                if (categoryCount == 1) {
                    throw new ApplicationException("Can't remove the last EventCategory from the filter");
                }
                if (categoriesMap.ContainsKey(category)) {
                    categoriesMap.Remove(category);
                    categoriesArray = null;
                    categoryCount--;
                }
            }
        }
    }

    /// <summary>
    /// Gets an array containing the values matched on by this instance
    /// </summary>
    public string[] Categories {
        get {
            lock (monitor) {
                if (categoriesArray == null) {
                    categoriesArray = new string[categoryCount];
                    IEnumerator e = categoriesMap.Keys.GetEnumerator();
                    for(int x = 0; x < categoryCount; x++) {
                        e.MoveNext();
                        if (e.Current == NULL) {
                            categoriesArray[x] = null;
                        }
                        else {
                            categoriesArray[x] = (string)e.Current;
                        }
                    }
                }
                string[] returnValue = new string[categoryCount];
                Array.Copy(categoriesArray, 0, returnValue, 0, categoryCount);
                return returnValue;
            }
        }
    }

    protected override bool Matches(Event _event) {
        if (isSingleCategory) {
            return (singleCategory.Equals(_event.Category));
        }
        else {
            if (_event.Category == null) {
                return categoriesMap.ContainsKey(NULL);
            }
            else {
                return (categoriesMap.ContainsKey(_event.Category));
            }
        }
    }

}

}